#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Compile AMOS proceedings into a single PDF with a cover page and table of contents.

Inputs:
- CSV metadata file (columns: year,title,authors,keywords,track,paper_url,pdf_url,pdf_path)
- Output PDF path
- Optional custom title/subtitle

It will:
1) Read CSV and filter rows with existing pdf_path
2) Sort entries by title
3) Compute page counts per paper
4) Generate cover.pdf and toc.pdf using reportlab
5) Merge cover, TOC, then all papers into one PDF
6) Add bookmarks (outlines) for each paper entry

Usage:
  python src/compile_proceedings.py --csv data/metadata_2025.csv --output data/AMOS_Proceedings_2025.pdf \
      --title "AMOS 2025 技术论文集" --subtitle "AMOS Technical Papers – 2025"
"""
from __future__ import annotations

import argparse
import csv
import os
import sys
from dataclasses import dataclass
from typing import List, Tuple

from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer
from reportlab.lib.enums import TA_CENTER, TA_LEFT

from pypdf import PdfReader, PdfWriter


@dataclass
class Entry:
    title: str
    authors: str
    pdf_path: str
    pages: int = 0
    start_page: int = 0  # 1-based in the final compiled PDF


def read_entries(csv_path: str) -> Tuple[int, List[Entry]]:
    entries: List[Entry] = []
    year: int = 0
    with open(csv_path, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            try:
                year = int(row.get('year') or year or 0)
            except Exception:
                pass
            title = (row.get('title') or '').strip()
            authors = (row.get('authors') or '').strip()
            pdf_path = (row.get('pdf_path') or '').strip()
            if not title or not pdf_path:
                continue
            if not os.path.exists(pdf_path):
                # Try relative to csv directory
                candidate = os.path.join(os.path.dirname(csv_path), pdf_path)
                if os.path.exists(candidate):
                    pdf_path = candidate
                else:
                    # skip missing files
                    continue
            entries.append(Entry(title=title, authors=authors, pdf_path=pdf_path))
    # sort by title
    entries.sort(key=lambda e: e.title.lower())
    return year, entries


def measure_pages(entries: List[Entry]) -> None:
    for e in entries:
        try:
            reader = PdfReader(e.pdf_path)
            e.pages = len(reader.pages)
        except Exception:
            e.pages = 0


def generate_cover(cover_path: str, main_title: str, subtitle: str, year: int) -> int:
    # One A4 page cover
    c = canvas.Canvas(cover_path, pagesize=A4)
    width, height = A4

    c.setFillColor(colors.HexColor('#0b3d91'))
    c.rect(0, 0, width, height, fill=1, stroke=0)

    c.setFillColor(colors.white)
    c.setFont('Helvetica-Bold', 36)
    c.drawCentredString(width/2, height*0.68, main_title)

    # Subtitle - check width and wrap if needed
    c.setFont('Helvetica', 20)
    subtitle_max_width = width - 60 * mm  # Leave margins
    if c.stringWidth(subtitle, 'Helvetica', 20) > subtitle_max_width:
        # Use smaller font if subtitle is too long
        c.setFont('Helvetica', 16)
        if c.stringWidth(subtitle, 'Helvetica', 16) > subtitle_max_width:
            # Split into two lines if still too long
            words = subtitle.split()
            mid = len(words) // 2
            line1 = ' '.join(words[:mid])
            line2 = ' '.join(words[mid:])
            c.drawCentredString(width/2, height*0.63, line1)
            c.drawCentredString(width/2, height*0.60, line2)
        else:
            c.drawCentredString(width/2, height*0.62, subtitle)
    else:
        c.drawCentredString(width/2, height*0.62, subtitle)

    c.setFont('Helvetica', 14)
    c.drawCentredString(width/2, height*0.56, f"Proceedings • {year}")

    c.setFont('Helvetica', 10)
    c.drawCentredString(width/2, height*0.08, "Compiled automatically from AMOS Technical Library metadata")

    c.showPage()
    c.save()
    return 1


def wrap_text(text: str, max_chars: int) -> List[str]:
    lines: List[str] = []
    cur = []
    for part in text.split():
        if sum(len(x) for x in cur) + len(cur) + len(part) > max_chars and cur:
            lines.append(' '.join(cur))
            cur = [part]
        else:
            cur.append(part)
    if cur:
        lines.append(' '.join(cur))
    return lines


def generate_toc(toc_path: str, entries: List[Entry], start_page_offset: int) -> int:
    # Create TOC pages in A4 with simple line layout
    c = canvas.Canvas(toc_path, pagesize=A4)
    width, height = A4
    left = 20 * mm
    right = width - 20 * mm
    top = height - 20 * mm
    bottom = 20 * mm

    header_style = ('Helvetica-Bold', 20)
    title_style = ('Helvetica-Bold', 11)
    author_style = ('Helvetica', 10)

    # Layout constants
    line_height = 13  # points
    max_title_chars = 75  # Reduced to make room for page numbers

    y = top

    def new_page():
        nonlocal y
        c.showPage()
        y = top

    # First page header
    c.setFont(*header_style)
    c.drawString(left, y, 'Table of Contents')
    y -= 24

    pages_used = 1

    for e in entries:
        # Title (may wrap)
        c.setFont(*title_style)
        title_lines = wrap_text(e.title, max_title_chars)
        for i, line in enumerate(title_lines):
            if y - line_height < bottom:
                new_page(); pages_used += 1
                c.setFont(*header_style)
                c.drawString(left, y, 'Table of Contents (cont.)')
                y -= 24
                c.setFont(*title_style)
            # For first line, show both title and page number
            if i == 0:
                page_num = str(e.start_page + start_page_offset)
                # Reserve space for page number (always 4 digits max + padding)
                page_area_width = 40
                title_max_width = right - left - page_area_width - 10
                
                # Truncate line if needed
                while c.stringWidth(line, *title_style) > title_max_width and len(line) > 10:
                    line = line[:-4] + '...'
                
                c.drawString(left, y, line)
                # Draw dotted leader
                title_end_x = left + c.stringWidth(line, *title_style)
                dots_start = title_end_x + 5
                dots_end = right - page_area_width
                if dots_start < dots_end - 10:
                    c.setDash(1, 3)
                    c.line(dots_start, y + 3, dots_end, y + 3)
                    c.setDash()
                # Draw page number in reserved area
                c.drawRightString(right, y, page_num)
            else:
                # Continuation lines (no page number)
                c.drawString(left + 5, y, line)
            y -= line_height
        # Authors (one line, clipped if too long)
        if e.authors:
            c.setFont(*author_style)
            # Extract first author only
            authors_line = e.authors
            if ';' in authors_line:
                authors_line = authors_line.split(';')[0].strip() + ' et al.'
            elif ',' in authors_line and authors_line.count(',') > 1:
                # Multiple authors separated by commas (e.g., "Name1, Org1, Name2, Org2")
                # Try to get first author with their organization
                parts = authors_line.split(',')
                if len(parts) >= 2:
                    authors_line = f"{parts[0].strip()}, {parts[1].strip()} et al."
                else:
                    authors_line = parts[0].strip() + ' et al.'
            
            # Calculate max width for authors (full width minus margins)
            max_author_width = right - left - 10
            # Truncate based on actual rendered width, not character count
            while c.stringWidth(authors_line, *author_style) > max_author_width and len(authors_line) > 20:
                authors_line = authors_line[:len(authors_line)-4] + '...'
            if y - line_height < bottom:
                new_page(); pages_used += 1
                c.setFont(*header_style)
                c.drawString(left, y, 'Table of Contents (cont.)')
                y -= 24
                c.setFont(*author_style)
            c.setFillColor(colors.gray)
            c.drawString(left, y, authors_line)
            c.setFillColor(colors.black)
            y -= line_height
        # spacer
        y -= 4

    c.showPage()
    c.save()
    return pages_used


def build_proceedings(csv_path: str, output_pdf: str, title: str | None, subtitle: str | None) -> None:
    year, entries = read_entries(csv_path)
    if not entries:
        raise RuntimeError('No entries with existing pdf_path found. Ensure CSV is correct and PDFs exist.')
    measure_pages(entries)

    # cover (1 page) first, then TOC (unknown pages), then papers
    cover_path = os.path.join(os.path.dirname(output_pdf), 'cover.tmp.pdf')
    toc_path = os.path.join(os.path.dirname(output_pdf), 'toc.tmp.pdf')

    ensure_parent_dir(output_pdf)

    main_title = title or f'AMOS {year} 技术论文集'
    sub_title = subtitle or f'AMOS Technical Papers – {year}'

    cover_pages = generate_cover(cover_path, main_title, sub_title, year)

    # Compute start pages: start after cover and TOC (TOC size computed after we know start pages => do two-phase)
    # First assume TOC pages = 1 to compute initial start_page; then generate TOC to know real pages; recompute.
    def assign_start_pages(extra_offset_pages: int):
        cur = 1 + extra_offset_pages  # cover + TOC
        for e in entries:
            e.start_page = cur
            cur += max(1, e.pages)
        return cur - 1  # last page number

    # Rough assignment with 1 TOC page
    assign_start_pages(1)
    # Generate TOC with correct page numbers (offset = cover_pages)
    toc_pages = generate_toc(toc_path, entries, start_page_offset=cover_pages)
    # Re-assign with actual TOC pages
    assign_start_pages(toc_pages)
    # Re-generate TOC to reflect final numbers
    toc_pages = generate_toc(toc_path, entries, start_page_offset=cover_pages)

    # Merge
    writer = PdfWriter()

    def append_pdf(path: str):
        r = PdfReader(path)
        for page in r.pages:
            writer.add_page(page)

    append_pdf(cover_path)
    append_pdf(toc_path)

    # Track cumulative page for bookmarks
    cumulative = cover_pages + toc_pages
    for e in entries:
        # Add outline (bookmark) to the starting page (0-based index)
        writer.add_outline_item(e.title, page_number=e.start_page - 1)
        # Append the document
        r = PdfReader(e.pdf_path)
        for page in r.pages:
            writer.add_page(page)
        cumulative += len(r.pages)

    with open(output_pdf, 'wb') as f:
        writer.write(f)

    # Cleanup temp files
    try:
        os.remove(cover_path)
    except Exception:
        pass
    try:
        os.remove(toc_path)
    except Exception:
        pass


def ensure_parent_dir(path: str) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description='Compile AMOS proceedings PDF with cover and TOC')
    ap.add_argument('--csv', required=True, help='Path to metadata CSV (e.g., data/metadata_2025.csv)')
    ap.add_argument('--output', required=True, help='Output PDF path (e.g., data/AMOS_Proceedings_2025.pdf)')
    ap.add_argument('--title', default=None, help='Cover title (default: AMOS <year> 技术论文集)')
    ap.add_argument('--subtitle', default=None, help='Cover subtitle (default: AMOS Technical Papers – <year>)')
    args = ap.parse_args(argv)

    build_proceedings(args.csv, args.output, args.title, args.subtitle)
    print(f"Wrote proceedings to {args.output}")
    return 0


if __name__ == '__main__':
    raise SystemExit(main())
